// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/type_environment.dart';

import '../base/messages.dart';
import '../base/problems.dart';
import '../base/scope.dart';
import '../builder/builder.dart';
import '../builder/builder_mixins.dart';
import '../builder/declaration_builders.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/type_builder.dart';
import '../kernel/type_algorithms.dart';
import 'source_library_builder.dart';
import 'source_loader.dart';
import 'source_member_builder.dart';
import 'source_type_parameter_builder.dart';

abstract class SourceDeclarationBuilder implements IDeclarationBuilder {
  void buildScopes(LibraryBuilder coreLibrary);

  int computeDefaultTypes(ComputeDefaultTypeContext context);
}

mixin SourceDeclarationBuilderMixin
    implements DeclarationBuilderMixin, SourceDeclarationBuilder {
  @override
  SourceLibraryBuilder get libraryBuilder;

  @override
  List<SourceNominalParameterBuilder>? get typeParameters;

  @override
  Uri get fileUri;

  /// Builds the [Extension] for this extension build and inserts the members
  /// into the [Library] of [libraryBuilder].
  ///
  /// [addMembersToLibrary] is `true` if the extension members should be added
  /// to the library. This is `false` if the extension is in conflict with
  /// another library member. In this case, the extension member should not be
  /// added to the library to avoid name clashes with other members in the
  /// library.
  void buildInternal(LibraryBuilder coreLibrary,
      {required bool addMembersToLibrary}) {
    ClassBuilder objectClassBuilder =
        coreLibrary.lookupLocalMember('Object', required: true) as ClassBuilder;

    void buildBuilders(String name, Builder declaration) {
      Builder? objectGetter = objectClassBuilder.lookupLocalMember(name);
      Builder? objectSetter =
          objectClassBuilder.lookupLocalMember(name, setter: true);
      if (objectGetter != null && !objectGetter.isStatic ||
          // Coverage-ignore(suite): Not run.
          objectSetter != null && !objectSetter.isStatic) {
        addProblem(
            // TODO(johnniwinther): Use a different error message for extension
            //  type declarations.
            templateExtensionMemberConflictsWithObjectMember
                .withArguments(name),
            declaration.fileOffset,
            name.length);
      }
      if (declaration.parent != this) {
        // Coverage-ignore-block(suite): Not run.
        if (fileUri != declaration.parent!.fileUri) {
          unexpected("$fileUri", "${declaration.parent!.fileUri}", fileOffset,
              fileUri);
        } else {
          unexpected(fullNameForErrors, declaration.parent!.fullNameForErrors,
              fileOffset, fileUri);
        }
      } else if (declaration is SourceMemberBuilder) {
        SourceMemberBuilder memberBuilder = declaration;
        memberBuilder.buildOutlineNodes((
            {required Member member,
            Member? tearOff,
            required BuiltMemberKind kind}) {
          _buildMember(memberBuilder, member, tearOff, kind,
              addMembersToLibrary: addMembersToLibrary);
        });
      } else {
        unhandled("${declaration.runtimeType}", "buildBuilders",
            declaration.fileOffset, declaration.fileUri);
      }
    }

    nameSpace.unfilteredNameIterator.forEach(buildBuilders);
    nameSpace.unfilteredConstructorNameIterator.forEach(buildBuilders);
  }

  int buildBodyNodes({required bool addMembersToLibrary}) {
    int count = 0;
    Iterator<SourceMemberBuilder> iterator = nameSpace
        .filteredIterator<SourceMemberBuilder>(includeDuplicates: false)
        .join(nameSpace.filteredConstructorIterator<SourceMemberBuilder>(
            includeDuplicates: false));
    while (iterator.moveNext()) {
      SourceMemberBuilder declaration = iterator.current;
      count += declaration.buildBodyNodes(
          // Coverage-ignore(suite): Not run.
          (
              {required Member member,
              Member? tearOff,
              required BuiltMemberKind kind}) {
        _buildMember(declaration, member, tearOff, kind,
            addMembersToLibrary: addMembersToLibrary);
      });
    }
    return count;
  }

  @override
  int computeDefaultTypes(ComputeDefaultTypeContext context) {
    bool hasErrors = context.reportNonSimplicityIssues(this, typeParameters);
    int count = context.computeDefaultTypesForVariables(typeParameters,
        inErrorRecovery: hasErrors);

    Iterator<SourceMemberBuilder> iterator =
        nameSpace.filteredConstructorIterator<SourceMemberBuilder>(
            includeDuplicates: false);
    while (iterator.moveNext()) {
      count += iterator.current
          .computeDefaultTypes(context, inErrorRecovery: hasErrors);
    }

    forEach((String name, Builder member) {
      if (member is SourceMemberBuilder) {
        count +=
            member.computeDefaultTypes(context, inErrorRecovery: hasErrors);
      } else {
        // Coverage-ignore-block(suite): Not run.
        assert(
            false,
            "Unexpected extension type member "
            "$member (${member.runtimeType}).");
      }
    });
    return count;
  }

  void checkTypesInOutline(TypeEnvironment typeEnvironment) {
    forEach((String name, Builder builder) {
      (builder as SourceMemberBuilder)
          .checkTypes(libraryBuilder, nameSpace, typeEnvironment);
    });

    nameSpace.forEachConstructor((String name, MemberBuilder builder) {
      (builder as SourceMemberBuilder)
          .checkTypes(libraryBuilder, nameSpace, typeEnvironment);
    });
  }

  void _buildMember(SourceMemberBuilder memberBuilder, Member member,
      Member? tearOff, BuiltMemberKind memberKind,
      {required bool addMembersToLibrary}) {
    if (!memberBuilder.isDuplicate && !memberBuilder.isConflictingSetter) {
      if (memberKind == BuiltMemberKind.ExtensionTypeRepresentationField) {
        addMemberInternal(memberBuilder, memberKind, member, tearOff);
      } else {
        if (addMembersToLibrary) {
          Reference addMember(Member member) {
            if (member is Field) {
              libraryBuilder.library.addField(member);
              return member.fieldReference;
            } else if (member is Procedure) {
              libraryBuilder.library.addProcedure(member);
              return member.reference;
            } else {
              unhandled("${member.runtimeType}", "buildBuilders",
                  member.fileOffset, member.fileUri);
            }
          }

          Reference memberReference = addMember(member);
          Reference? tearOffReference;
          if (tearOff != null) {
            tearOffReference = addMember(tearOff);
          }
          addMemberDescriptorInternal(
              memberBuilder, memberKind, memberReference, tearOffReference);
        } else {
          // Still set parent to avoid crashes.
          member.parent = libraryBuilder.library;
        }
      }
    }
  }

  /// Adds [member] and [tearOff] to this declaration.
  void addMemberInternal(SourceMemberBuilder memberBuilder,
      BuiltMemberKind memberKind, Member member, Member? tearOff);

  /// Adds a descriptor for [member] to this declaration.
  void addMemberDescriptorInternal(
      SourceMemberBuilder memberBuilder,
      BuiltMemberKind memberKind,
      Reference memberReference,
      Reference? tearOffReference);

  @override
  List<DartType> buildAliasedTypeArguments(LibraryBuilder library,
      List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy) {
    if (arguments == null && typeParameters == null) {
      return <DartType>[];
    }

    if (arguments == null && typeParameters != null) {
      List<DartType> result =
          new List<DartType>.generate(typeParameters!.length, (int i) {
        if (typeParameters![i].defaultType == null) {
          throw 'here';
        }
        return typeParameters![i].defaultType!.buildAliased(
            library, TypeUse.defaultTypeAsTypeArgument, hierarchy);
      }, growable: true);
      return result;
    }

    if (arguments != null && arguments.length != typeParametersCount) {
      // Coverage-ignore-block(suite): Not run.
      assert(libraryBuilder.loader.assertProblemReportedElsewhere(
          "SourceDeclarationBuilderMixin.buildAliasedTypeArguments: "
          "the numbers of type parameters and type arguments don't match.",
          expectedPhase: CompilationPhaseForProblemReporting.outline));
      return unhandled(
          templateTypeArgumentMismatch
              .withArguments(typeParametersCount)
              .problemMessage,
          "buildTypeArguments",
          -1,
          null);
    }

    assert(arguments!.length == typeParametersCount);
    List<DartType> result =
        new List<DartType>.generate(arguments!.length, (int i) {
      return arguments[i]
          .buildAliased(library, TypeUse.typeArgument, hierarchy);
    }, growable: true);
    return result;
  }

  @override
  int get typeParametersCount => typeParameters?.length ?? 0;
}
